<?php

namespace api\libs\cdb;

use Yii;
use yii\helpers\Json;
use api\components\helpers\Constant;
use api\models\Application;

/**
 * 兼容nginx环境 (nginx没有该函数）
 */
if (!function_exists('getallheaders')) {

    function getallheaders()
    {
        foreach ($_SERVER as $name => $value) {
            if (substr($name, 0, 5) == 'HTTP_') {
                $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
            }
        }
        return $headers;
    }

}

/**
 * Communcation Interface Platform类
 *
 * 该类为单件类
 *
 * @author johnson
 * @package cip.extensions.cdb
 */
class CdbService
{

    private static $_instance = NULL;
    private $_headers = NULL;
    private $_key = NULL;
    private $_accessSignature = NULL;
    private $_app_id = NULL;
    private $_token = NULL;
    public $is_open = true;


    private function __construct()
    {
        $this->_headers = getallheaders();
        $this->_headers['appid'] = isset($this->_headers['appid']) ? $this->_headers['appid'] : $this->_headers['Appid'];
        $this->_headers['token'] = isset($this->_headers['token']) ? $this->_headers['token'] : $this->_headers['Token'];
        $this->_headers['Authorization'] = isset($this->_headers['token']) ? $this->_headers['token'] : $this->_headers['Token'];
        if (isset($this->_headers['appid']) && isset($this->_headers['token'])) {
            $this->_app_id = $this->_headers['appid'];
            $this->_token = $this->_headers['token'];
        } else {
            $result = ['code' => 401, 'message' => '缺少账户信息', 'status' => Constant::API_STATUS_FAILED];
            Yii::$app->response->data = $result;
            Yii::$app->response->format = 'json';
            Yii::$app->response->send();
            Yii::$app->end();
        }
        $config_white_list = include_once Yii::getAlias("@common") . "/config/auth_white_list.php";
        $whitelists = !empty($this->getWhiteList()) ? $this->getWhiteList() : $config_white_list;
//        if (in_array($this->get_client_ip(), $whitelists)) {
        if (in_array(Yii::$app->request->getHostName(), $whitelists)) {
            $this->is_open = false;
        } else {
            if (!isset($this->_headers['Authorization']) || empty($this->_headers['Authorization'])) {
                $result = ['code' => 401, 'message' => '缺少签名信息', 'status' => Constant::API_STATUS_FAILED];
                Yii::$app->response->data = $result;
                Yii::$app->response->format = 'json';
                Yii::$app->response->send();
                Yii::$app->end();
            }
            if (strpos($this->_headers['Authorization'], ':') !== false) {
                list($service, $accessSignature) = explode(":", $this->_headers['Authorization']);
                $this->_accessSignature = $accessSignature;
            }
        }
    }

    private function getWhiteList()
    {
        $whiteList = [];
        $apps = Application::find()->select('allowips')->where(['status' => Constant::STATUS_VALID, 'is_signature' => false, 'app_id' => $this->_headers['appid']])->all();
        foreach ($apps as $app) {
            if (!empty($app->allowips)) {
                $whiteList = array_merge($whiteList, explode(',', $app->allowips));
            }
        }
        return array_unique($whiteList);
    }

    /**
     * 创建类的实例
     */
    public static function instance()
    {
        if (self::$_instance === NULL) {
            self::$_instance = new CdbService();
        }
        return self::$_instance;
    }

    public function getKey()
    {
        return $this->_key;
    }

    public function getAppId()
    {
        return $this->_app_id;
    }

    public function getToken()
    {
        return $this->_token;
    }

    /**
     * 校验认证信息
     * @param string $secret 验证码
     * @return void
     */
    public function checkAuth($secret)
    {
        if ($this->is_open) {
            $stringToSign = '';
            $stringToSign .= $_SERVER['REQUEST_METHOD'] . "\n";

            $sigParams = array(
                'content-md5' => '',
                'content-type' => '',
                'date' => ''
            );
            uksort($this->_headers, 'strnatcasecmp');
            foreach ($this->_headers as $key => $value) {
                $value = str_replace(array("\r", "\n"), '', $value);
                $key = strtolower($key);
                if (in_array($key, array('content-md5', 'content-type', 'date'), true)) {
                    $sigParams[$key] = $value;
                    if ($key == 'content-type' && strpos($value, 'multipart/form-data') !== false) {
                        $sigParams[$key] = 'multipart/form-data';
                    }
                }
            }
            $stringToSign .= implode("\n", $sigParams) . "\n";

            $requestUrl = parse_url($_SERVER['REQUEST_URI']);

            $path = $requestUrl['path'];
            if (substr($requestUrl['path'], -1) != '/')
                $stringToSign .= $requestUrl['path'];
            else
                $stringToSign .= $requestUrl['path'];
            $signature = base64_encode(hash_hmac('sha1', $stringToSign, $secret, true));
            if ($signature !== $this->_accessSignature) {
                $result = ['code' => 401, 'message' => '签名认证失败', 'status' => Constant::API_STATUS_FAILED];
                Yii::$app->response->data = $result;
                Yii::$app->response->format = 'json';
                Yii::$app->response->send();
                Yii::$app->end();
            }
        }
    }

    /**
     * 发送json格式响应
     * @param int $status HTTP状态
     * @param array $data 需要发送的数据，数组格式
     */
    public function sendJsonResponse($status, $data)
    {
        $this->sendResponse($status, Json::encode($data), 'application/json');
    }

    /**
     * 发送响应
     *
     * @param int $status 状态码
     * @param string $body 主题内容
     * @param string $content_type 内容类型
     * @return void
     */
    public function sendResponse($status = 200, $body = '', $content_type = 'text/html')
    {
        $status_header = 'HTTP/1.1 ' . $status . ' ' . $this->_getStatusCodeMessage($status);
        header($status_header);
        header('Content-type: ' . $content_type);
        header('Access-Control-Allow-Origin: *');

        if (isset($this->_headers['Galaxy-Extension-Header'])) {
            header('Access-Control-Allow-Origin: *');
        }

        // pages with body are easy
        if ($body != '') {
            echo $body;
            exit;
        } else {
            $message = '';

            // this is purely optional, but makes the pages a little nicer to read
            // for your users.  Since you won't likely send a lot of different status codes,
            // this also shouldn't be too ponderous to maintain
            switch ($status) {
                case 401:
                    $message = 'You must be authorized to view this page.';
                    break;
                case 404:
                    $message = 'The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found.';
                    break;
                case 500:
                    $message = 'The server encountered an error processing your request.';
                    break;
                case 501:
                    $message = 'The requested method is not implemented.';
                    break;
            }

            // servers don't always have a signature turned on (this is an apache directive "ServerSignature On")
            $signature = empty($_SERVER['SERVER_SIGNATURE']) ? $_SERVER['SERVER_SOFTWARE'] . ' Server at ' . $_SERVER['SERVER_NAME'] . ' Port ' . $_SERVER['SERVER_PORT'] : $_SERVER['SERVER_SIGNATURE'];

            // this should be templatized in a real-world solution
            $body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
                        <html>
                            <head>
                                <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
                                <title>' . $status . ' ' . $this->_getStatusCodeMessage($status) . '</title>
                            </head>
                            <body>
                                <h1>' . $this->_getStatusCodeMessage($status) . '</h1>
                                <p>' . $message . '</p>
                                <hr />
                                <address>' . $signature . '</address>
                            </body>
                        </html>';

            echo $body;
            exit;
        }
    }

    /**
     * 消息状态码转换
     *
     * @param int $status 状态码 如:200
     * @access private
     * @return string
     */
    private function _getStatusCodeMessage($status)
    {
        // via parse_ini_file()... however, this will suffice
        // for an example
        $codes = Array(
            100 => 'Continue',
            101 => 'Switching Protocols',
            200 => 'OK',
            201 => 'Created',
            202 => 'Accepted',
            203 => 'Non-Authoritative Information',
            204 => 'No Content',
            205 => 'Reset Content',
            206 => 'Partial Content',
            300 => 'Multiple Choices',
            301 => 'Moved Permanently',
            302 => 'Found',
            303 => 'See Other',
            304 => 'Not Modified',
            305 => 'Use Proxy',
            306 => '(Unused)',
            307 => 'Temporary Redirect',
            400 => 'Bad Request',
            401 => 'Unauthorized',
            402 => 'Payment Required',
            403 => 'Forbidden',
            404 => 'Not Found',
            405 => 'Method Not Allowed',
            406 => 'Not Acceptable',
            407 => 'Proxy Authentication Required',
            408 => 'Request Timeout',
            409 => 'Conflict',
            410 => 'Gone',
            411 => 'Length Required',
            412 => 'Precondition Failed',
            413 => 'Request Entity Too Large',
            414 => 'Request-URI Too Long',
            415 => 'Unsupported Media Type',
            416 => 'Requested Range Not Satisfiable',
            417 => 'Expectation Failed',
            500 => 'Internal Server Error',
            501 => 'Not Implemented',
            502 => 'Bad Gateway',
            503 => 'Service Unavailable',
            504 => 'Gateway Timeout',
            505 => 'HTTP Version Not Supported'
        );

        return (isset($codes[$status])) ? $codes[$status] : '';
    }

    function get_client_ip()
    {
        $IP = '';
        if (getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
            $IP = getenv('HTTP_CLIENT_IP');
        } elseif (getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
            $IP = getenv('HTTP_X_FORWARDED_FOR');
        } elseif (getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
            $IP = getenv('REMOTE_ADDR');
        } elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
            $IP = $_SERVER['REMOTE_ADDR'];
        }
        return $IP ? $IP : "unknow";
    }

}
